/*
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Autogenerated file -- DO NOT EDIT!

#ifndef LIBPANDAFILE_TYPE_H_
#define LIBPANDAFILE_TYPE_H_

namespace panda::panda_file {

class Type {
public:
    enum class TypeId : uint8_t {
% PandaFile::types.each do |type|
        <%= type.name.upcase %> = 0x<%= type.code.to_s(16) %>,
%end
    };

    static_assert(TypeId::I8 < TypeId::I16);
    static_assert(TypeId::I16 < TypeId::I32);
    static_assert(TypeId::I32 < TypeId::I64);
    static_assert(TypeId::I64 < TypeId::REFERENCE);

    static_assert(TypeId::U8 < TypeId::U16);
    static_assert(TypeId::U16 < TypeId::U32);
    static_assert(TypeId::U32 < TypeId::U64);
    static_assert(TypeId::U64 < TypeId::REFERENCE);

    static_assert(TypeId::I32 < TypeId::F64);
    static_assert(TypeId::U32 < TypeId::F64);
    static_assert(TypeId::F64 < TypeId::REFERENCE);

    constexpr explicit Type(TypeId id) : type_(id) {}

    constexpr bool IsPrimitive() const {
        return type_ != TypeId::REFERENCE;
    }

    constexpr bool IsReference() const {
        return type_ == TypeId::REFERENCE;
    }

    constexpr uint8_t GetEncoding() const {
        return static_cast<uint8_t>(type_);
    }

    constexpr bool IsSigned() const {
        switch (type_) {
% PandaFile::types.each do |type|
        case TypeId::<%= type.name.upcase %>:
            return <%= type.properties.include? "signed" %>;
%end
        }

        UNREACHABLE_CONSTEXPR();
    }

    constexpr size_t GetBitWidth() const {
        switch (type_) {
% PandaFile::types.each do |type|
        case TypeId::<%= type.name.upcase %>: {
            constexpr size_t BITWIDTH = <%= type.width ? type.width : 0 %>;
            return BITWIDTH;
        }
%end
        }

        UNREACHABLE_CONSTEXPR();
    }

    constexpr bool IsNumeric() const {
        switch (type_) {
% PandaFile::types.each do |type|
        case TypeId::<%= type.name.upcase %>:
            return <%= type.properties.include? "numeric" %>;
%end
        }

        UNREACHABLE_CONSTEXPR();
    }

    constexpr bool IsFloat() const {
        switch (type_) {
% PandaFile::types.each do |type|
        case TypeId::<%= type.name.upcase %>:
            return <%= type.properties.include? "float" %>;
%end
        }

        UNREACHABLE_CONSTEXPR();
    }

    constexpr bool IsIntegral() const {
        switch (type_) {
% PandaFile::types.each do |type|
        case TypeId::<%= type.name.upcase %>:
            return <%= type.properties.include? "integral" %>;
%end
        }

        UNREACHABLE_CONSTEXPR();
    }

    constexpr bool IsVoid() const {
        return type_ == TypeId::VOID;
    }

    static const char* GetSignatureByTypeId(Type type) {
        auto id = type.GetId();
        switch (id) {
        case panda_file::Type::TypeId::VOID:
            return "V";
        case panda_file::Type::TypeId::U1:
            return "Z";
        case panda_file::Type::TypeId::I8:
            return "B";
        case panda_file::Type::TypeId::U8:
            return "H";
        case panda_file::Type::TypeId::I16:
            return "S";
        case panda_file::Type::TypeId::U16:
            return "C";
        case panda_file::Type::TypeId::I32:
            return "I";
        case panda_file::Type::TypeId::U32:
            return "U";
        case panda_file::Type::TypeId::I64:
            return "J";
        case panda_file::Type::TypeId::U64:
            return "Q";
        case panda_file::Type::TypeId::F32:
            return "F";
        case panda_file::Type::TypeId::F64:
            return "D";
        case panda_file::Type::TypeId::REFERENCE:
            return "L";
        case panda_file::Type::TypeId::TAGGED:
            return "A";
        default:
            UNREACHABLE();
        }
    }

    static Type GetTypeIdBySignature(char signature) {
        switch (signature) {
        case 'V':
            return Type(panda_file::Type::TypeId::VOID);
        case 'Z':
            return Type(panda_file::Type::TypeId::U1);
        case 'B':
            return Type(panda_file::Type::TypeId::I8);
        case 'H':
            return Type(panda_file::Type::TypeId::U8);
        case 'S':
            return Type(panda_file::Type::TypeId::I16);
        case 'C':
            return Type(panda_file::Type::TypeId::U16);
        case 'I':
            return Type(panda_file::Type::TypeId::I32);
        case 'U':
            return Type(panda_file::Type::TypeId::U32);
        case 'F':
            return Type(panda_file::Type::TypeId::F32);
        case 'D':
            return Type(panda_file::Type::TypeId::F64);
        case 'J':
            return Type(panda_file::Type::TypeId::I64);
        case 'Q':
            return Type(panda_file::Type::TypeId::U64);
        case 'A':
            return Type(panda_file::Type::TypeId::TAGGED);
        case 'L':
        case '[':
            return Type(panda_file::Type::TypeId::REFERENCE);
        default:
            UNREACHABLE();
        }
    }

    /*
     * For field types following encoding is used:
     *
% PandaFile::types.each do |type|
%   field_encoding = type.code - 2
%   if field_encoding >= 0
     * <%= type.name %>: 0x<%= field_encoding.to_s(16) %>,
%   end
%end
     */
    constexpr uint8_t GetFieldEncoding() const {
        return GetEncoding() - static_cast<uint8_t>(TypeId::U1);
    }

    constexpr TypeId GetId() const {
        return type_;
    }

    constexpr bool operator==(const Type &other) const {
        return type_ == other.type_;
    }

    constexpr bool operator!=(const Type &other) const {
        return type_ != other.type_;
    }

    static constexpr Type GetTypeFromFieldEncoding(uint32_t field_encoding) {
        uint8_t ref_encoding = Type(TypeId::REFERENCE).GetFieldEncoding();
        uint8_t last_encoding = Type(TypeId::TAGGED).GetFieldEncoding();
        if (field_encoding == last_encoding) {
            return Type(TypeId::TAGGED);
        }
        if (field_encoding > last_encoding || field_encoding == ref_encoding) {
            // The case when field's type encoding is >= sizeof(Header).
            // It means the field_encoding is a reference type which refers to Class or ForeignClass
            // See file_format.md FieldType paragraph
            return Type(TypeId::REFERENCE);
        }

        return Type(static_cast<TypeId>(field_encoding + static_cast<uint8_t>(TypeId::U1)));
    }

    friend std::ostream &operator<<(std::ostream &stream, const Type &type) {
        switch (type.type_) {
% PandaFile::types.each do |type|
        case TypeId::<%= type.name.upcase %>:
            stream << "<%= PandaFile::asm_name(type) %>";
            break;
%end
        }
        return stream;
    }

private:
    TypeId type_;
};

}  // namespace panda::panda_file

#endif  // LIBPANDAFILE_TYPE_H_
